#include "Arbol.h"
#include "../../recursoDeAlmacenamiento/FabricaRecursosAlmacenamiento.h"
#include "BloqueHoja.h"
#include <queue>
#include <cmath>

cantBytes Arbol::calcularTamanio(cantBytes orden, cantBytes tamanioMaxClave) {

	return BloqueArbol::calcularTamanio(orden, tamanioMaxClave);
}

Arbol::Arbol(string nombre, TipoClave* extractor, int orden,
		cantBytes tamanioMaxClave) {
	std::string urlIndice = nombre;
	urlIndice += "_";
	urlIndice += extractor->toString() + ".bsi";
	this->raiz = NULL;
	this->nombre = nombre;

	tamanioBloque = calcularTamanio(orden, tamanioMaxClave);
	this->recurso
			= FabricaRecursosAlmacenamiento::getFabrica()->crearRecursoAccesoDirecto(
					urlIndice, tamanioBloque);
	this->hojaActual = NULL;
	this->actual = 0;
	this->extractor = extractor;
	this->CANT_MAX_CLAVES = (orden - 1);
	this->inicializado = false;

}

Arbol::~Arbol() {

	delete this->recurso;

}

BloqueArbol* Arbol::crearRaiz() {

	BloqueArbol* nuevo = new BloqueHoja(this, tamanioBloque, extractor);
	nuevo->setId(ID_RAIZ);
	return nuevo;
}

bool Arbol::estaVacio() {
	return (this->raiz == NULL);
}

unsigned Arbol::getCantClavesMax() {

	return CANT_MAX_CLAVES;
}

void Arbol::setRaiz(offset idNueva) {
	if (idNueva != ID_NULL) {
		if ((idNueva != ID_RAIZ) && (idNueva != ID_NULL)) {
			BloqueArbol* nuevaRaiz = this->getLibre(idNueva);
			this->raiz->setModificado();

			this->raiz->setId(idNueva);

			this->raiz->setModificado();
			ultimosAccedidos.push_back(this->raiz);

			nuevaRaiz->setId(ID_RAIZ);
			nuevaRaiz->setPadre(ID_NULL);
			this->raiz = nuevaRaiz;
			this->raiz->setModificado();

		}
	}
}



void Arbol::irInicio() {
	if (this-> raiz != NULL) {
		hojaActual = raiz->getHijoMenor();
		actual = dynamic_cast<BloqueHoja*> (hojaActual)->getPunteros()[0];
	}
}

Elemento* Arbol::proximo() {
	if (hojaActual != NULL) {
		Elemento* e = NULL;
		std::vector<offset> ve =
				dynamic_cast<BloqueHoja*> (hojaActual)->getPunteros();
		std::vector<offset>::iterator it = ve.begin();
		int pos = 0;
		while ((it != ve.end()) && ((*it) <= actual)) {
			++it;
			pos++;
		}
		e =  new Elemento(Key(hojaActual->getClaves()[pos-1]),ve[pos-1]);
		if (it != ve.end()) {

			actual = (*it);
		}
		else {
			BloqueHoja* h =	dynamic_cast<BloqueHoja*> (hojaActual)->getHermano();
			if (h != NULL) {
				hojaActual = h;
				actual = h->getPunteros()[0];
			} else {
				hojaActual = NULL;
			}
		}
		return e;
	}
	return NULL;
}

bool Arbol::tieneProximo() {
	return (hojaActual != NULL) ;
}

BloqueArbol* Arbol::getNew(char tipo) {
	BloqueArbol* nuevo = NULL;
	if (tipo == 'H') {
		nuevo = new BloqueHoja(this, tamanioBloque, extractor);
	} else {
		nuevo = new BloqueInterno(this, tamanioBloque, extractor);
	}
	this->inicializarBloque(nuevo);
	ultimosAccedidos.push_front(nuevo);
	return nuevo;
}

bool Arbol::inicializar() {
	Bytes bytes1 = this->recurso->cargar(ID_RAIZ);
	Bytes bytes2 = this->recurso->cargar(ID_NULL);
	bytes1.agregarAlFinal(bytes2);

	if (bytes1.getTamanio() == 0) {
		return false;
	}

	if (bytes1.getSubBytes(0, 1).toString() == "I") {
		BloqueInterno* bloque = new BloqueInterno(this, 0, this->extractor);
		bloque->hidratarse(bytes1);

		this->raiz->setTamanio(this->raiz->getTamanio() / 2);
		this->raiz = bloque;
		return true;
	} else if (bytes1.getSubBytes(0, 1).toString() == "H") {
		BloqueHoja* bloque = new BloqueHoja(this, 0, this->extractor);
		bloque->hidratarse(bytes1);

		this->raiz->setTamanio(this->raiz->getTamanio() / 2);
		this->raiz = bloque;
		return true;
	} else {
		cerr
				<< "Error: Se leyo un registro que no es ni Hoja, ni bloque interno"
				<< endl;
	}

	return false;
}

BloqueArbol* Arbol::get(offset o) {
	if (o == ID_RAIZ) {
		if (this->raiz != NULL) {
			return this->raiz;
		} else {
			cerr << "Arbol Inconsistente No Tiene Raiz " << __FILE__
					<< __LINE__ << endl;
		}
	}
	list<BloqueArbol*>::iterator it = ultimosAccedidos.begin();
	while (it != ultimosAccedidos.end()) {
		if (o == (*it)->getId()) {
			return (*it);
		}
		it++;
	}
	BloqueArbol* temp = this->traerBloqueArbol(o);
	ultimosAccedidos.push_front(temp);
	return temp;
}

BloqueArbol* Arbol::getLibre(offset o) {
	list<BloqueArbol*>::iterator it = ultimosAccedidos.begin();
	while (it != ultimosAccedidos.end()) {
		if (o == (*it)->getId()) {
			BloqueArbol* temp = (*it);
			ultimosAccedidos.erase(it);
			return temp;
		}
		it++;
	}
	return this->traerBloqueArbol(o);
}

bool Arbol::guardarRaiz() {
	if (this->raiz != NULL) {
		cantBytes tam = raiz->getTamanio();
		tam *= 2;
		this->raiz->setTamanio(tam);
		Bytes s = this->raiz->serializarse();
		Bytes b1 = s.getSubBytes(0, tamanioBloque);
		Bytes b0 = s.getSubBytes(tamanioBloque, tamanioBloque);
		this->recurso->guardar(b0, ID_NULL);
		this->recurso->guardar(b1, ID_RAIZ);
		tam /= 2;
		this->raiz->setTamanio(tam);
		return true;
	}
	return false;
}

void Arbol::borrarRaiz() {
	if (this->raiz != NULL) {
		recurso->eliminar(ID_RAIZ);
	}
}

BloqueArbol* Arbol::traerBloqueArbol(offset nroComponente) {
	Bytes bytes = this->recurso->cargar(nroComponente);

	if (bytes.getTamanio() == 0) {
		return NULL;
	}

	if (bytes.getSubBytes(0, 1).toString() == "I") {
		BloqueInterno* bloque = new BloqueInterno(this, 0, this->extractor);
		bloque->hidratarse(bytes);

		return bloque;
	} else if (bytes.getSubBytes(0, 1).toString() == "H") {
		BloqueHoja* bloque = new BloqueHoja(this, 0, this->extractor);
		bloque->hidratarse(bytes);

		return bloque;
	} else {
		cerr
				<< "Error: Se leyo un registro que no es ni Hoja, ni bloque interno"
				<< endl;
	}

	return NULL;

}

void Arbol::guardarModificado(BloqueArbol* bloque) {
	if (bloque == NULL) {
		cerr << "Error: Se intento modificar un bloque nulo" << endl;
	} else {
		this->recurso->guardar(bloque->serializarse(), bloque->getId());
	}
}

void Arbol::inicializarBloque(BloqueArbol* bloque) {
	if (bloque == NULL) {
		cerr << "Error: Se intento guardar un bloque nulo" << endl;

	} else {

		offset off = this->recurso->guardar(bloque->serializarse());
		bloque->setId(off);
		bloque->setModificado();
	}
}

void Arbol::ultimosAdisco() {
	/* La raiz la guardo siempre*/
	this->guardarRaiz();
	/*Guardo solo los bloques que se modificaron*/
	for (list<BloqueArbol*>::iterator it = ultimosAccedidos.begin(); it
			!= ultimosAccedidos.end(); ++it) {
		if ((*it)->getModificado()) {
			this->guardarModificado((*it));
		}
		delete (*it);
	}
	ultimosAccedidos.clear();
}

BloqueArbol* Arbol::getRaiz() {
	return raiz;
}

Resultado Arbol::insertar(const Elemento& elementoAInsertar) {
	bool overflow = false;
	/*si es el primer elemento a insertar: */
	Resultado res;

	if (!raiz) {
		/*Es el primerElemento*/
		this->raiz = dynamic_cast<BloqueHoja*> (this->crearRaiz());
		/*si no tiene padre se retorna a si mismo y si tiene lo retorna a el*/
		raiz->insertarElemento(elementoAInsertar, res, overflow);
		this->ultimosAdisco();
		return res;
	} else {

		/*debo insertar recursivamente*/
		raiz->insertarElemento(elementoAInsertar, res, overflow);
		this->setRaiz(this->raiz->getAdan());

		this->ultimosAdisco();

		return res;
	}

}

Elemento* Arbol::buscar(const Key& clave, Resultado& res) {
	if (this->raiz != NULL) {
		res = RES_OK;
		offset off = this->raiz->getElemento(clave, res);
		if (off == ID_NULL) {
			res = RES_INEXISTENTE;
			this->ultimosAdisco();
			return NULL;
		}
		res = RES_OK;
		Elemento* elementoEncontrado = new Elemento(clave, off);
		this->ultimosAdisco();
		return elementoEncontrado;
	} else {
		res = RES_INEXISTENTE;
		this->ultimosAdisco();
		return NULL;
	}
}

Resultado Arbol::borrar(Elemento& elemento) {
	Resultado res;
	if (!this->raiz) {
		/*el arbol no tiene elementos*/
		return RES_INEXISTENTE;
	} else {

		raiz->removerElemento(elemento, res);

		if (raiz->getBorrado() == true) {
			BloqueInterno* raizInt = dynamic_cast<BloqueInterno*> (raiz);
			if (raizInt != NULL) {
				/*es inerno*/
				this->setRaiz(raizInt->getPunteros()[0]);
			}
		}
		this->setRaiz(getRaiz()->getAdan());
		if (getRaiz()->getCantClaves() == 0) {
			/*la clave era la unica de la raiz=> arbol vacio*/
			delete (raiz);
			this->raiz = NULL;

		}
		this->ultimosAdisco();
		return res;
	}
}

void Arbol::visualizar() {
	if (raiz != NULL) {
		std::cout << "EL ARBOL: " << std::endl;
		this->raiz->imprimir();
	} else {
		std::cout << "EL ARBOL NO TIENE ELEMENTOS " << std::endl;

	}
}
